﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms.VisualStyles;
using TriangleNet.Meshing.Algorithm;
using TriangleNet;
using TriangleNet.Geometry;

namespace LightCAD.MathLib.CSG
{
    partial class Mesh
    {

        //        typedef GenericVertType*    GVptr;
        //typedef IsctVertType*           IsctVertType;
        //typedef OrigVertType*	        OVptr;
        //typedef GenericEdgeType*    GEptr;
        //typedef IsctEdgeType*           IsctEdgeType;
        //typedef OrigEdgeType*	        OEptr;
        //typedef SplitEdgeType*	        SEptr;
        //typedef GenericTriType*     GTptr;

        //typedef GluePointMarker*    GluePointMarker;

       class TriangleProblem
        {
            public List<IsctVertType> iverts=new List<IsctVertType>();
            public List<IsctEdgeType> iedges=new List<IsctEdgeType>();
            // original triangle elements
            public OrigVertType[] overts = new OrigVertType[3];
            public OrigEdgeType[] oedges = new OrigEdgeType[3];

            public List<GenericTriType> gtris = new List<GenericTriType>();

            public TopoTri the_tri;
            public void init(IsctProblem iprob, TopoTri t)
            {
                the_tri = t;
                // extract original edges/verts
                for (int k = 0; k < 3; k++)
                    overts[k] = iprob.newOrigVert(the_tri.verts[k]);
                for (int k = 0; k < 3; k++)
                {
                    oedges[k] = iprob.newOrigEdge(the_tri.edges[k],
                                                  overts[(k + 1) % 3],
                                                  overts[(k + 2) % 3]);
                }
            }

            void addEdge(IsctProblem iprob, IsctVertType iv, TopoTri tri_key)
            {
                IsctEdgeType ie = find_edge(iedges, tri_key);
                if (ie != null)
                { // if the edge is already present
                    ie.ends[1] = iv;
                    iv.edges.push_back(ie);
                }
                else
                { // if the edge is being added
                    ie = iprob.newIsctEdge(iv, tri_key);
                    iedges.push_back(ie);
                }
            }
            void addBoundaryHelper(TopoEdge edge, IsctVertType iv)
            {
                iv.boundary = true;
                iverts.push_back(iv);
                // hook up point to boundary edge interior!
                for (int k = 0; k < 3; k++)
                {
                    OrigEdgeType oe = oedges[k];
                    if (oe.concrete == edge)
                    {
                        oe.interior.push_back(iv);
                        iv.edges.push_back(oe);
                        break;
                    }
                }
            }

            void subdivideEdge(IsctProblem iprob, GenericEdgeType ge, List<GenericEdgeType> edges)
            {
                if (ge.interior.size() == 0)
                {
                    //if(typeid(ge) == typeid(IsctEdgeType)) { // generate new edge
                    //    iprob.buildConcreteEdge(ge);
                    //}
                    edges.push_back(ge);
                }
                else if (ge.interior.size() == 1)
                { // common case
                    var se0 = iprob.newSplitEdge(ge.ends[0],
                                                ge.interior[0],
                                                ge.boundary);
                    var se1 = iprob.newSplitEdge(ge.interior[0],
                                                ge.ends[1],
                                                ge.boundary);
                    //iprob.buildConcreteEdge(se0);
                    //iprob.buildConcreteEdge(se1);
                    edges.push_back(se0);
                    edges.push_back(se1);

                    // get rid of old edge
                    iprob.releaseEdge(ge);
                }
                else
                { // sorting is the uncommon case
                  // determine the primary dimension and direction of the edge
                    Vec3 dir = ge.ends[1].coord - ge.ends[0].coord;
                    int dim = (Math.Abs(dir.x) > Math.Abs(dir.y)) ?
                                            ((Math.Abs(dir.x) > Math.Abs(dir.z)) ? 0 : 2) :
                                            ((Math.Abs(dir.y) > Math.Abs(dir.z)) ? 1 : 2);
                    double sign = (dir[dim] > 0.0) ? 1.0 : -1.0;

                    // pack the interior vertices into a vector for sorting
                    var verts = new List<KeyValuePair<double, IsctVertType>>();
                    for (int ind = 0; ind != ge.interior.size(); ++ind)
                    {
                        var iv = ge.interior[ind];
                        verts.push_back(new KeyValuePair<double, IsctVertType>(
                            // if the sort is ascending, then we're good...
                            sign * iv.coord[dim], iv));
                    }
                    // ... and sort the vector
                   // verts.Sort();
                    //std::sort(verts.begin(), verts.end());
                    // then, write the verts into a new container with the endpoints
                    var allv = new List<GenericVertType>().makeSize(verts.size() + 2);
                    allv[0] = ge.ends[0];
                    allv[allv.size() - 1] = ge.ends[1];
                    for (int k = 0; k < verts.size(); k++)
                        allv[k + 1] = verts[k].Value;

                    // now create and accumulate new split edges
                    for (int i = 1; i < allv.size(); i++)
                    {
                        var se = iprob.newSplitEdge(allv[i - 1],
                                                              allv[i],
                                                              ge.boundary);
                        edges.push_back(se);
                    }
                    // get rid of old edge
                    iprob.releaseEdge(ge);
                }
            }



            // specify reference glue point and edge piercing this triangle.
            public IsctVertType addInteriorEndpoint(IsctProblem iprob, TopoEdge edge, GluePointMarker glue)
            {
                var iv = iprob.newIsctVert(edge, the_tri, glue);
                iv.boundary = false;
                iverts.push_back(iv);
                for (int ind = 0; ind != edge.tris.size(); ++ind)
                {
                    addEdge(iprob, iv, edge.tris[ind]);
                }
                return iv;
            }
            // specify the other triangle cutting this one, the edge cut,
            // and the resulting point of intersection
            public void addBoundaryEndpoint(IsctProblem iprob, TopoTri tri_key, TopoEdge edge, IsctVertType iv)
            {
                iv = iprob.copyIsctVert(iv);
                addBoundaryHelper(edge, iv);
                // handle edge extending into interior
                addEdge(iprob, iv, tri_key);
            }
            public IsctVertType addBoundaryEndpoint(IsctProblem iprob, TopoTri tri_key, TopoEdge edge, Vec3 coord, GluePointMarker glue)
            {
                IsctVertType iv = iprob.newSplitIsctVert(coord, glue);
                addBoundaryHelper(edge, iv);
                // handle edge extending into interior
                addEdge(iprob, iv, tri_key);
                return iv;
            }
            // Should only happen for manually inserted split points on
            // edges, not for points computed via intersection...
            public IsctVertType addBoundaryPointAlone(IsctProblem iprob, TopoEdge edge, Vec3 coord, GluePointMarker glue)
            {
                IsctVertType iv = iprob.newSplitIsctVert(coord, glue);
                addBoundaryHelper(edge, iv);
                return iv;
            }
            public void addInteriorPoint(IsctProblem iprob, TopoTri t0, TopoTri t1, GluePointMarker glue)
            {
                // note this generates wasted re-computation of coordinates 3X
                IsctVertType iv = iprob.newIsctVert(the_tri, t0, t1, glue);
                iv.boundary = false;
                iverts.push_back(iv);
                // find the 2 interior edges
                for (int ind = 0; ind != iedges.size(); ++ind)
                {
                    var ie = iedges[ind];
                    if (ie.other_tri_key == t0 ||
                       ie.other_tri_key == t1)
                    {
                        ie.interior.push_back(iv);
                        iv.edges.push_back(ie);
                    }
                }
            }

            // run after we've accumulated all the elements
            public void consolidate(IsctProblem iprob)
            {
                // identify all intersection edges missing endpoints
                // and check to see if we can assign an original vertex
                // as the appropriate endpoint.
                for (int ind = 0; ind != iedges.size(); ++ind)
                {
                    IsctEdgeType ie = iedges[ind];
                    if (ie.ends[1] == null)
                    {
                        // try to figure tout which vertex must be the endpoint...
                        TopoVert vert = commonVert(the_tri, ie.other_tri_key);
                        if (vert == null)
                        {
                            //                            std::cout << "the  edge is "
                            //                                      << ie.ends[0] << ",  "
                            //                                      << ie.ends[1] << std::endl;
                            //                            IsctVertType iv = dynamic_cast<IsctVertType>(ie.ends[0]);
                            //                            std::cout << "   "
                            //                                      << iv.glue_marker.edge_tri_type
                            //                                      << std::endl;
                            //# ifdef SUPPORT_TOPO_STREAM_OPERATORS
                            //                            std::cout << "the   tri is " << the_tri << ": "
                            //                                      << *the_tri << std::endl;
                            //                            std::cout << "other tri is " << ie.other_tri_key << ": "
                            //                                      << *(ie.other_tri_key) << std::endl;
                            //#endif
                            //                            std::cout << "coordinates for triangles" << std::endl;
                            //                            std::cout << "the tri" << std::endl;
                            //                            for (int k = 0; k < 3; k++)
                            //                                std::cout << iprob.vPos(the_tri.verts[k])
                            //                                          << std::endl;
                            //                            for (int k = 0; k < 3; k++)
                            //                                std::cout << iprob.vPos(ie.other_tri_key.verts[k])
                            //                                          << std::endl;
                            //std::cout << "degen count:"
                            //    << empty3d.degeneracy_count << std::endl;
                            //std::cout << "exact count: "
                            //    << empty3d.exact_count << std::endl;
                        }
                        //assert(vert);
                        Utils.ENSURE(vert != null); // bad if we can't find a common vertex
                                                    // then, find the corresponding OVptr, and connect
                        for (int k = 0; k < 3; k++)
                        {
                            if (overts[k].concrete == vert)
                            {
                                ie.ends[1] = overts[k];
                                overts[k].edges.push_back(ie);
                                break;
                            }
                        }
                    }
                }

                //assert(isValid());
                Utils.ENSURE(isValid());
            }



            IsctEdgeType find_edge(List<IsctEdgeType> vec, TopoTri key)
            {
                for (int ind = 0; ind != vec.size(); ++ind)
                {
                    var ie = vec[ind];
                    if (ie.other_tri_key == key)
                        return ie;
                }
                return null;
            }
            public TopoVert commonVert(TopoTri t0, TopoTri t1)
            {
                for (int i = 0; i < 3; i++)
                {
                    for (int j = 0; j < 3; j++)
                    {
                        if (t0.verts[i] == t1.verts[j])
                            return t0.verts[i];
                    }
                }
                return null;
            }

           
            public bool isValid() { return the_tri != null; }

            public void subdivide(IsctProblem iprob)
            {
                // collect all the points, and create more points as necessary
                var points = new List<GenericVertType>();
                for (int k = 0; k < 3; k++)
                {
                    points.push_back(overts[k]);
                    //std::cout << k << ": id " << overts[k].concrete.ref << std::endl;
                }
                for (int ind = 0; ind != iverts.size(); ++ind)
                {
                    IsctVertType iv = iverts[ind];
                    //iprob.buildConcreteVert(iv);
                    points.push_back(iv);
                    /*std::cout << "  " << points.size() - 1
                                  << " (" << iv.glue_marker.edge_tri_type
                                  << ") ";
                    if(iv.glue_marker.edge_tri_type) {
                        TopoEdge e = iv.glue_marker.e;
                        Vec3 p0 = iprob.vPos(e.verts[0]);
                        Vec3 p1 = iprob.vPos(e.verts[1]);
                        std::cout << " "
                                  << e.verts[0].ref << p0
                                  << "  " << e.verts[1].ref << p1;

                        TopoTri t = iv.glue_marker.t[0];
                        p0 = iprob.vPos(t.verts[0]);
                        p1 = iprob.vPos(t.verts[1]);
                        Vec3 p2 = iprob.vPos(t.verts[2]);
                        std::cout << "        "
                                  << t.verts[0].ref << p0
                                  << "  " << t.verts[1].ref << p1
                                  << "  " << t.verts[2].ref << p2;
                    }
                    std::cout
                                  << std::endl;*/
                }
                for (int i = 0; i < points.size(); i++)
                    points[i].idx = i;

                // split edges and marshall data
                // for safety, we zero tout references to pre-subdivided edges,
                // which may have been destroyed
                var edges = new List<GenericEdgeType>();
                for (int k = 0; k < 3; k++)
                {
                    //std::cout << "oedge:  "
                    //          << oedges[k].ends[0].idx << "; "
                    //          << oedges[k].ends[1].idx << std::endl;
                    //for(IsctVertType iv : oedges[k].interior)
                    //    std::cout << "  " << iv.idx << std::endl;
                    subdivideEdge(iprob, oedges[k], edges);
                    oedges[k] = null;
                }
                //std::cout << "THE TRI: " << the_tri.verts[0].ref
                //          << "; " << the_tri.verts[1].ref
                //          << "; " << the_tri.verts[2].ref
                //          << std::endl;
                for (int ind = 0; ind != iedges.size(); ++ind)
                {
                    IsctEdgeType ie = iedges[ind];
                    //std::cout << "iedge:  "
                    //          << ie.ends[0].idx << "; "
                    //          << ie.ends[1].idx << std::endl;
                    //std::cout << "other tri: " << ie.other_tri_key.verts[0].ref
                    //          << "; " << ie.other_tri_key.verts[1].ref
                    //          << "; " << ie.other_tri_key.verts[2].ref
                    //          << std::endl;
                    //for(IsctVertType iv : ie.interior)
                    //    std::cout << "  " << iv.idx 
                    //              << " (" << iv.glue_marker.edge_tri_type
                    //              << ") " << std::endl;
                    subdivideEdge(iprob, ie, edges);
                    iedges[ind] = null;
                }
                for (int i = 0; i < edges.size(); i++)
                    edges[i].idx = i;

                // find 2 dimensions to project onto
                // get normal
                Vec3 normal = Vec3.cross(overts[1].coord - overts[0].coord,
                                      overts[2].coord - overts[0].coord);
                int normdim = Vec3.abs(normal.clone()).maxDim();
                int dim0 = (normdim + 1) % 3;
                int dim1 = (normdim + 2) % 3;
                double sign_flip = (normal[normdim] < 0.0) ? -1.0 : 1.0;



                // Choose triangulator: Incremental, SweepLine or Dwyer.
                var triangulator = new Incremental();

                var vertics = new List<Vertex>(points.Count);
                for(var i=0;i<points.Count;i++)
                {
                    var p = points[i];
                    vertics.Add(new Vertex(p.coord[dim0], p.coord[dim1]* sign_flip, p.boundary ? 1 : 0));
                }
                // Generate mesh.
                var mesh = triangulator.Triangulate(vertics, new Configuration());

                /*

                triangulateio tin = new triangulateio();
                triangulateio tout = new triangulateio();

                // Define input points. 
                tin.numberofpoints = points.size();
                tin.numberofpointattributes = 0;
                tin.pointlist = new List<double>().makeSize(tin.numberofpoints * 2);
                tin.pointattributelist = null;
                tin.pointmarkerlist = new List<int>().makeSize(tin.numberofpoints);
                for (int k = 0; k < tin.numberofpoints; k++)
                {
                    tin.pointlist[k * 2 + 0] = points[k].coord[dim0];
                    tin.pointlist[k * 2 + 1] = points[k].coord[dim1] * sign_flip;
                    tin.pointmarkerlist[k] = (points[k].boundary) ? 1 : 0;
                }

                //Define the input segments 
                tin.numberofsegments = edges.size();
                tin.numberofholes = 0;// yes, zero
                tin.numberofregions = 0;// not using regions
                tin.segmentlist = new List<int>().makeSize(tin.numberofsegments * 2);
                tin.segmentmarkerlist = new List<int>().makeSize(tin.numberofsegments);
                for (int k = 0; k < tin.numberofsegments; k++)
                {
                    tin.segmentlist[k * 2 + 0] = edges[k].ends[0].idx;
                    tin.segmentlist[k * 2 + 1] = edges[k].ends[1].idx;
                    tin.segmentmarkerlist[k] = (edges[k].boundary) ? 1 : 0;
                }

                // to be safe... declare 0 triangle attributes on input
                tin.numberoftriangles = 0;
                tin.numberoftriangleattributes = 0;

                //set for flags.... 
                tout.pointlist = null;
                tout.pointattributelist = null; // not necessary if using -N or 0 attr
                tout.pointmarkerlist = null;
                tout.trianglelist = null; // not necessary if using -E
                                          //tout.triangleattributelist = null; // not necessary if using -E or 0 attr
                                          //tout.trianglearealist = // only needed with -r and -a
                                          //tout.neighborlist = null; // only neccesary if -n is used
                tout.segmentlist = null; // NEED THIS; output segments go here
                tout.segmentmarkerlist = null; // NEED THIS for OUTPUT SEGMENTS
                                               //tout.edgelist = null; // only necessary if -e is used
                                               //tout.edgemarkerlist = null; // only necessary if -e is used

                // solve the triangulation problem
                var args = "pzQYY";
                //char *debug_params = (char*)("pzYYVC");
                triangulate (args, tin, tout, null);

                if (tout.numberofpoints != tin.numberofpoints)
                {
                    //std::cout << "tout.numberofpoints: "
                    //          << tout.numberofpoints << std::endl;
                    //std::cout << "points.size(): " << points.size() << std::endl;
                    //std::cout << "dumping tout the points' coordinates" << std::endl;
                    //for (int k = 0; k < points.size(); k++)
                    //{
                    //    GVptr gv = points[k];
                    //    std::cout << "  " << gv.coord
                    //                  << "  " << gv.idx << std::endl;
                    //}

                    //std::cout << "dumping tout the segments" << std::endl;
                    //for (int k = 0; k < tin.numberofsegments; k++)
                    //    std::cout << "  " << tin.segmentlist[k * 2 + 0]
                    //              << "; " << tin.segmentlist[k * 2 + 1]
                    //              << " (" << tin.segmentmarkerlist[k]
                    //              << ") " << std::endl;

                    //std::cout << "dumping tout the solved for triangles now..."
                    //          << std::endl;
                    //for (int k = 0; k < tout.numberoftriangles; k++)
                    //{
                    //    std::cout << "  "
                    //              << tout.trianglelist[(k * 3) + 0] << "; "
                    //              << tout.trianglelist[(k * 3) + 1] << "; "
                    //              << tout.trianglelist[(k * 3) + 2] << std::endl;
                    //}
                }
                Utils.ENSURE(tout.numberofpoints == tin.numberofpoints);

                //std::cout << "number of points tin: " << tin.numberofpoints
                //          << std::endl;
                //std::cout << "number of edges tin: " << tin.numberofsegments
                //          << std::endl;
                //std::cout << "number of triangles tout: " << tout.numberoftriangles
                //          << std::endl;
                */
                
                var triList = mesh.Triangles.ToList();
                for (int k = 0; k < triList.Count; k++)
                {
                    var tri = triList[k];
                    GenericVertType gv0 = points[tri.GetVertexID(0)];
                    GenericVertType gv1 = points[tri.GetVertexID(1)];
                    GenericVertType gv2 = points[tri.GetVertexID(2)];
                    if (Math.Abs(getArea(gv0.coord, gv1.coord, gv2.coord, dim0, dim1)) < 1E-8) continue;

                    gtris.Add(iprob.newGenericTri(gv0, gv1, gv2));
                }


                // clean up after triangulate...
                // tin free
                //free(tin.pointlist);
                //free(tin.pointmarkerlist);
                //free(tin.segmentlist);
                //free(tin.segmentmarkerlist);
                //// tout free
                //free(tout.pointlist);
                ////free(tout.pointattributelist);
                //free(tout.pointmarkerlist);
                //free(tout.trianglelist);
                ////free(tout.triangleattributelist);
                ////free(tout.trianglearealist);
                ////free(tout.neighborlist);
                //free(tout.segmentlist);
                //free(tout.segmentmarkerlist);
                //free(tout.edgelist);
                //free(tout.edgemarkerlist);
            }
            public double getArea(Vec3 p1, Vec3 p2, Vec3 p3, int d0,int d1)
            {
                return 0.5 * (p1[d0] * p2[d1] + p2[d0] * p3[d1] + p3[d0] * p1[d1]
                    - p1[d0] * p3[d1] - p2[d0] * p1[d1] - p3[d0] * p2[d1]);
            }
            private void triangulate(string args, triangulateio tin, triangulateio tout, object value)
            {
                throw new NotImplementedException();
            }
        }
    }
}
